package com.example.gb28181.request.impl;

import cn.hutool.core.util.StrUtil;
import com.example.gb28181.enums.SipMethodEnum;
import com.example.gb28181.properties.SipProperties;
import com.example.gb28181.request.SIPRequestProcessor;
import com.example.gb28181.service.SIPProcessorObserver;
import com.example.gb28181.utils.MD5Util;
import gov.nist.javax.sip.RequestEventExt;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.header.Expires;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.sip.*;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.*;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.text.ParseException;
import java.util.Calendar;

/**
 * @E-mail cwm3412638@163.com
 * @Author cwm
 * @Date 2022/7/21
 */
@Component
@AllArgsConstructor
@Slf4j
public class RegisterRequestProcessor implements InitializingBean, SIPRequestProcessor {
    private final SipProperties sipProperties;
    private final SIPProcessorObserver sipProcessorObserver;

    private final static SipMethodEnum REGISTER = SipMethodEnum.REGISTER;

    private final SipFactory sipFactory;

    @Override
    @Async
    public void process(RequestEvent event) throws SipException, InvalidArgumentException, ParseException {
        //转换请求事件为扩展事件
        if (event instanceof RequestEventExt) {
            RequestEventExt evtExt = (RequestEventExt) event;
            //获取请求对象设备id
            String deviceId = getDeviceIdByRequest(evtExt.getRequest());
            //获取sip协议提供者和工厂
            SipProvider sipProvider = (SipProvider) event.getSource();
            HeaderFactory headerFactory = sipFactory.createHeaderFactory();
            MessageFactory messageFactory = sipFactory.createMessageFactory();
            AddressFactory addressFactory = sipFactory.createAddressFactory();
            log.info("【收到注册请求】,【地址：】{},【端口：】{}【设备id:】,{}", evtExt.getRemoteIpAddress(), evtExt.getRemotePort(), deviceId);
            if (StrUtil.isEmpty(deviceId)) {
                log.error("【error:】{}", "设备id为空");
            }
            if (StrUtil.isEmpty(sipProperties.getPassword()) || isAuthorizationPass(event.getRequest(), sipProperties.getPassword())) {
                log.info("【设备id：】{},注册成功", deviceId);
                doSuccess(event, messageFactory, headerFactory, sipProvider);
            } else if (isRegisterWithoutAuth(evtExt.getRequest())) {
                log.info("【设备id：】{},为鉴权401", deviceId);
                doUnAuthorized401(event, messageFactory, headerFactory, sipProvider, deviceId);
            } else {
                doLoginFail403(event, addressFactory, messageFactory, headerFactory, sipProvider);
            }
        }
    }

    /**
     * spring 容器加载完成调用方法 把注册处理器放入到 sip处理器观察者类中
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        // 添加消息处理的订阅
        sipProcessorObserver.addRequestProcessor(REGISTER, this);
    }

    /**
     * 获取当前请求的设备id
     *
     * @param request
     * @return
     */
    private String getDeviceIdByRequest(Request request) {
        ToHeader toHead = (ToHeader) request.getHeader(ToHeader.NAME);
        SipURI toUri = (SipURI) toHead.getAddress().getURI();
        return toUri.getUser();
    }

    /**
     * 有Auth信息，一般在第二次Register的时候，这个时候会带着第一次服务端返回的Digest信息
     *
     * @param request
     * @return
     */
    private boolean isRegisterWithoutAuth(Request request) {
        int expires = request.getExpires().getExpires();
        AuthorizationHeader authorizationHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
        return expires > 0 && authorizationHeader == null;
    }

    /**
     * 组装登录成功200的Response
     *
     * @return
     */
    private void doSuccess(RequestEvent requestEvent, MessageFactory messageFactory, HeaderFactory headerFactory, SipProvider sipProvider) throws ParseException, SipException, InvalidArgumentException {
        Request request = requestEvent.getRequest();
        Response response = messageFactory.createResponse(Response.OK, request);
        DateHeader dateHeader = headerFactory.createDateHeader(Calendar.getInstance());
        response.addHeader(dateHeader);
        ServerTransaction serverTransactionId = requestEvent.getServerTransaction() == null ? sipProvider.getNewServerTransaction(request) : requestEvent.getServerTransaction();
        serverTransactionId.sendResponse(response);
    }

    /**
     * 响应未鉴权401
     *
     * @param requestEvent
     * @param messageFactory
     * @param headerFactory
     * @param sipProvider
     * @param deviceId
     * @throws ParseException
     * @throws SipException
     * @throws InvalidArgumentException
     */
    private void doUnAuthorized401(RequestEvent requestEvent, MessageFactory messageFactory, HeaderFactory headerFactory, SipProvider sipProvider, String deviceId) throws ParseException, SipException, InvalidArgumentException {
        Response response;
        response = messageFactory.createResponse(Response.UNAUTHORIZED, requestEvent.getRequest());
        String realm = MD5Util.generateShortUUID();

        String callId = getCallIdFromRequest(requestEvent.getRequest());
        String nonce = MD5Util.MD5(callId + deviceId);
        WWWAuthenticateHeader wwwAuthenticateHeader = headerFactory.createWWWAuthenticateHeader("Digest realm=\"" + realm + "\",nonce=\"" + nonce + "\",algorithm=MD5");
        response.setHeader(wwwAuthenticateHeader);
        ServerTransaction serverTransactionId = requestEvent.getServerTransaction() == null ? sipProvider.getNewServerTransaction(requestEvent.getRequest()) : requestEvent.getServerTransaction();
        serverTransactionId.sendResponse(response);
    }


    /**
     * 组装登录失败403的Response
     *
     * @return
     */
    private void doLoginFail403(RequestEvent requestEvent, AddressFactory addressFactory, MessageFactory messageFactory, HeaderFactory headerFactory, SipProvider sipProvider) throws ParseException, SipException, InvalidArgumentException {
        Request request = requestEvent.getRequest();
        Response response = messageFactory.createResponse(Response.FORBIDDEN, request);
        DateHeader dateHeader = headerFactory.createDateHeader(Calendar.getInstance());
        response.addHeader(dateHeader);
        ServerTransaction serverTransactionId = requestEvent.getServerTransaction() == null ? sipProvider.getNewServerTransaction(request) : requestEvent.getServerTransaction();
        serverTransactionId.sendResponse(response);
    }

    /**
     * 是否校验鉴权通过
     *
     * @param request
     * @return true 通过
     */
    private boolean isAuthorizationPass(Request request, String password) {
        if (isRegisterWithoutAuth(request)) {
            return false;
        }
        AuthorizationHeader authorizationHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
        String username = authorizationHeader.getUsername();
        String realm = authorizationHeader.getRealm();
        String nonce = authorizationHeader.getNonce();
        URI uri = authorizationHeader.getURI();
        String res = authorizationHeader.getResponse();
        String algorithm = authorizationHeader.getAlgorithm();
        log.info("Authorization信息：username=" + username + ",realm=" + realm + ",nonce=" + nonce + ",uri=" + uri + ",response=" + res + ",algorithm=" + algorithm);
        if (null == username || null == realm || null == nonce || null == uri || null == res || null == algorithm) {
            log.error("Authorization信息不全，无法认证。");
            return false;
        } else {
            // 比较Authorization信息正确性
            String A1 = MD5Util.MD5(username + ":" + realm + ":" + password);
            String A2 = MD5Util.MD5("REGISTER" + ":" + uri);
            String resStr = MD5Util.MD5(A1 + ":" + nonce + ":" + A2);
            return resStr.equals(res);
        }
    }

    /**
     * 由本地设备(Client)生成，全局唯一，每次呼叫这个值唯一不变
     *
     * @param request
     * @return
     */
    private String getCallIdFromRequest(Request request) {
        CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
        return callIdHeader.getCallId();
    }
}
